use crate::export::device;
use core::ptr::copy_nonoverlapping;
use utils::defer_ref;
use utils::ufmt;

#[derive(Debug, ufmt::derive::uDebug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Sector {
    pub base: u32,
    pub len: u32,
}

impl Sector {
    pub const fn new(base: u32, len: u32) -> Self {
        Self { base, len }
    }
}
const _16KB: u32 = 16 * 1024;
const _64KB: u32 = 64 * 1024;
const _128KB: u32 = 128 * 1024;

const FLASH_BASE: u32 = 0x08000000;

pub const S0: Sector = Sector::new(FLASH_BASE, _16KB);
pub const S1: Sector = Sector::new(S0.base + S0.len, _16KB);
pub const S2: Sector = Sector::new(S1.base + S1.len, _16KB);
pub const S3: Sector = Sector::new(S2.base + S2.len, _16KB);
pub const S4: Sector = Sector::new(S3.base + S3.len, _64KB);

pub const S5: Sector = Sector::new(S4.base + S4.len, _128KB);
pub const S6: Sector = Sector::new(S5.base + S5.len, _128KB);

pub const S7: Sector = Sector::new(S6.base + S6.len, _128KB);
pub const S8: Sector = Sector::new(S7.base + S7.len, _128KB);

pub const S9: Sector = Sector::new(S8.base + S8.len, _128KB);
pub const S10: Sector = Sector::new(S9.base + S9.len, _128KB);
pub const S11: Sector = Sector::new(S10.base + S10.len, _128KB);

pub const SECTORS: [Sector; 12] = [S0, S1, S2, S3, S4, S5, S6, S7, S8, S9, S10, S11];

const FLASH_KEY1: u32 = 0x45670123;
const FLASH_KEY2: u32 = 0xCDEF89AB;

#[derive(Debug, Clone, Copy, ufmt::derive::uDebug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Error {
    InvalidAddress,
    OPERR,
    WRPERR,
    PGAERR,
    PGPERR,
    PGSERR,
    Write,
}

fn unlock() {
    let flash = unsafe { device::FLASH::steal() };
    flash.keyr().write(|w| w.key().set(FLASH_KEY1));
    flash.keyr().write(|w| w.key().set(FLASH_KEY2));

    flash.sr().write(|w| { 
        w.pgserr().clear()
        .pgperr().clear()
        .pgaerr().clear()
        .wrperr().clear()
        .operr().clear()
        .eop().clear()
    });
}

fn lock() {
    let flash = unsafe { device::FLASH::steal() };
    flash.cr().modify(|_, w| w.lock().locked());
}

fn wait_done() -> Result<(), Error> {
    let flash = unsafe { device::FLASH::steal() };
    loop {
        let status = flash.sr().read();
        if status.operr().is_active() {
            return Err(Error::OPERR);
        }
        if status.wrperr().is_active() {
            return Err(Error::WRPERR);
        }
        if status.pgaerr().is_active() {
            return Err(Error::PGAERR);
        }
        if status.pgperr().is_active() {
            return Err(Error::PGPERR);
        }
        if status.pgserr().is_active() {
            return Err(Error::PGSERR);
        }
        if status.bsy().is_not_busy() {
            break;
        }
    }
    Ok(())
}

fn sector_by_addr(addr: u32) -> Result<(usize, &'static Sector), Error> {
    for sector in SECTORS.iter().enumerate() {
        if (addr >= sector.1.base) && (addr < (sector.1.base + sector.1.len)) {
            return Ok(sector);
        }
    }
    Err(Error::InvalidAddress)
}

fn erase_by_id(id: u8) -> Result<(), Error> {
    let flash = unsafe { device::FLASH::steal() };

    defer_ref!({
        flash.acr().modify(|_, w| w.dcen().enabled());
    });
    flash.acr().modify(|_, w| w.dcen().disabled());

    wait_done()?;

    defer_ref!({
        flash.cr().modify(|_, w| w.ser().clear_bit());
    });

    flash.cr().modify(|_, w| unsafe {
        w.psize()
            .psize32()
            .snb()
            .bits(id)
            .ser()
            .sector_erase()
            .strt()
            .start()
    });

    wait_done()?;

    Ok(())
}

pub fn erase(addr: u32, len: u32) -> Result<(), Error> {
    defer_ref!({ lock() });
    unlock();

    let mut index = 0;

    while index < len {
        let sector = sector_by_addr(addr + index)?;
        erase_by_id(sector.0 as u8)?;
        index += sector.1.len;
    }

    Ok(())
}

trait Writeable {
    fn write_with_addr(self, addr: u32);

    fn read_with_addr(addr: u32) -> Self;

    fn reset() {
        let flash = unsafe { device::FLASH::steal() };
        flash.cr().modify(|_, w| w.pg().clear_bit());
    }
}

impl Writeable for u8 {
    fn read_with_addr(addr: u32) -> Self {
        unsafe { (addr as *const u8).read_volatile() }
    }

    fn write_with_addr(self, addr: u32) {
        let flash = unsafe { device::FLASH::steal() };
        flash.cr().modify(|_, w| w.psize().psize8().pg().program());
        unsafe {
            (addr as *mut u8).write_volatile(self);
        }
    }
}

impl Writeable for u16 {
    fn read_with_addr(addr: u32) -> Self {
        unsafe { (addr as *const u16).read_volatile() }
    }

    fn write_with_addr(self, addr: u32) {
        let flash = unsafe { device::FLASH::steal() };
        flash.cr().modify(|_, w| w.psize().psize16().pg().program());
        unsafe {
            (addr as *mut u16).write_volatile(self);
        }
    }
}

impl Writeable for u32 {
    fn read_with_addr(addr: u32) -> Self {
        unsafe { (addr as *const u32).read_volatile() }
    }

    fn write_with_addr(self, addr: u32) {
        let flash = unsafe { device::FLASH::steal() };
        flash.cr().modify(|_, w| w.psize().psize32().pg().program());
        unsafe {
            (addr as *mut u32).write_volatile(self);
        }
    }
}

fn write_value<T: Writeable + Eq + Copy>(addr: u32, value: T) -> Result<(), Error> {
    wait_done()?;
    value.write_with_addr(addr);
    let ret = wait_done();
    T::reset();
    ret?;
    if T::read_with_addr(addr) != value {
        return Err(Error::Write);
    }
    Ok(())
}

pub fn write(addr: u32, buf: &[u8]) -> Result<(), Error> {
    let mut index = 0;
    let len = buf.len();
    let data = buf.as_ptr();

    unlock();
    defer_ref!({
        lock();
    });

    while index < len {
        let n = len - index;
        if n >= 4 {
            let value = unsafe {
                u32::from_ne_bytes([
                    data.add(index).read(),
                    data.add(index + 1).read(),
                    data.add(index + 2).read(),
                    data.add(index + 3).read(),
                ])
            };
            write_value(addr + index as u32, value)?;
            index += 4;
        } else if n >= 2 {
            let value =
                unsafe { u16::from_ne_bytes([data.add(index).read(), data.add(index + 1).read()]) };
            write_value(addr + index as u32, value)?;
            index += 2;
        } else if n >= 1 {
            let value = unsafe { u8::from_ne_bytes([data.add(index).read()]) };
            write_value(addr + index as u32, value)?;
            index += 1;
        }
    }

    Ok(())
}

pub fn read(addr: u32, buf: &mut [u8]) {
    unsafe {
        copy_nonoverlapping(addr as *const u8, buf.as_mut_ptr(), buf.len());
    }
}
